/* === LICENSE INFO ===
 * This source file is subject to the GPLv3 license that is bundled with this
 * package in the file LICENSE.txt.
 * It is also available through the world-wide-web at this URL:
 * http://www.gnu.org/licenses/gpl.txt
 * === end of LICENSE INFO === */

/*==============================================================================
  File Name   : LogFileWriter.cpp
  Description : Definition for LogFileWriter class in GLog module.

  Created on  : 2009-10-03
  Updated on  : 2011-01-30
  Updated by  : Lee Shiou Ming

  Cautions    : (important notes)
  ============================================================================*/

//------------------------------------------------------------------------------
//-- INCLUDES & DEFINES
//------------------------------------------------------------------------------
#include "LogFileWriter.hpp"
#include "GDateTime/GDateTime.hpp"
#include <iostream>

using namespace gUtil::GLog;
namespace fs = boost::filesystem;


//------------------------------------------------------------------------------
//-- INITIALIZATIONS
//------------------------------------------------------------------------------


//------------------------------------------------------------------------------
//-- FUNCTION IMPLEMENTATIONS
//------------------------------------------------------------------------------
LogFileWriter::LogFileWriter(
    const std::string& _logFilePath,
    const unsigned int _logRotationSize,
    const TimeInterval _logRotationInterval)
:
    m_logFilePath(_logFilePath, fs::native),
    m_logRotationSize(_logRotationSize),
    m_logRotationInterval(_logRotationInterval),
    m_logFileSize(0)
{
    bool hasError = false;
    std::string errMessage = "";
    if (fs::exists(m_logFilePath))
    {
        if (fs::is_directory(m_logFilePath))
        {
            hasError = true;
            errMessage = "Directory with the same name already exists.";
        }
        else if (fs::is_symlink(m_logFilePath))
        {
            hasError = true;
            errMessage = "Symbolic link with the same name already exists.";
        }
        else if (!fs::is_regular_file(m_logFilePath))
        {
            hasError = true;
            errMessage = "Non regular file with the same name already exists.";
        }
    }

    if (hasError)
    {
        throw std::runtime_error(
                "Failed to initialize log file writer. " + errMessage);
    }

    try
    {
        if (fs::exists(m_logFilePath))
        {
            m_logFileSize = fs::file_size(m_logFilePath);
            // TODO: Decouple boost date_time from LogFileWriter::LogFileWriter().
            // Different platform may have different output. On OS like Windows,
            // file time attribute returned will be in UTC.
            m_lastLogMsgTime = boost::posix_time::from_time_t(
                    fs::last_write_time(m_logFilePath));
        }

        if (isLogRotationRequired(0, GDateTime::getCurrentTime()))
        {
            rotateLogFile();
        }
        else
        {
            openLogFile();
        }

        m_lastLogMsgTime = GDateTime::getCurrentTime();
    }
    catch (const std::exception& e)
    {
        std::string errMessage = "Failed to initialize log file writer. ";
        errMessage += e.what();
        throw std::runtime_error(errMessage);
    }
}


LogFileWriter::~LogFileWriter()
{
    closeLogFile();
}


void LogFileWriter::setLogRotationSize(unsigned int _logRotationSize)
{
    m_logRotationSize = _logRotationSize;
}


void LogFileWriter::setLogRotationInterval(TimeInterval _logRotationInterval)
{
    m_logRotationInterval = _logRotationInterval;
}


void LogFileWriter::append(const std::string& _logMessage,
        const std::string& _logLevelString,
        const ptime& _logMsgTime)
{
    std::string logMessage = formatLogMessage(_logMessage, _logLevelString,
            _logMsgTime);

    try
    {
        unsigned int logMessageSize = 0;
        if (m_logRotationSize > 0)
        {
            // Plus one for EOL character.
            logMessageSize = (logMessage.length() + 1) * LOG_CHAR_SIZE;
        }

        if (isLogRotationRequired(logMessageSize, _logMsgTime))
        {
            rotateLogFile();
        }

        m_logFileStream << logMessage;
        m_logFileStream.flush();

        // In MT env, there is a chance for newer log message to be processed
        // earlier, depends on scheduling of the system.
        if (m_lastLogMsgTime < _logMsgTime)
        {
            m_lastLogMsgTime = _logMsgTime;
        }

        m_logFileSize += logMessageSize;
    }
    catch (const std::exception& e)
    {
        std::cerr << "[ERROR] " << __PRETTY_FUNCTION__ <<
                ": Caught exception - " << e.what() << std::endl;
    }
}


inline void LogFileWriter::openLogFile()
{
    m_logFileStream.open(m_logFilePath.string(), std::ios::out | std::ios::app);
    if (m_logFileStream.fail())
    {
        throw std::runtime_error(
                "Encounter error while opening log file stream.");
    }
}


inline void LogFileWriter::closeLogFile()
{
    if (m_logFileStream.is_open())
    {
        m_logFileStream.flush();
        m_logFileStream.close();
    }
}


inline bool LogFileWriter::isLogRotationRequired(
        const unsigned int _logMsgSize,
        const GDateTime::ptime& _logMsgTime)
{
    // Log file rotation by size and by interval are both disabled.
    if ((m_logRotationInterval == TIME_INTERVAL_NONE) &&
        (m_logRotationSize <= 0))
    {
        return false;
    }

    // Check if log file size is hitting rotation threshold.
    if ((m_logRotationSize > 0) &&
        (m_logFileSize + _logMsgSize >= m_logRotationSize))
    {
        return true;
    }

    // Check if now is the time to do file rotation.
    if ((m_logRotationInterval != TIME_INTERVAL_NONE) &&
        (_logMsgTime > m_lastLogMsgTime))
    {
        // TODO: Try to decouple boost date_time from LogFileWriter
        tm logMsgTm = boost::posix_time::to_tm(_logMsgTime);
        tm lastLogMsgTm = boost::posix_time::to_tm(m_lastLogMsgTime);

        switch (m_logRotationInterval)
        {
            case TIME_INTERVAL_HOURLY:
                if ((logMsgTm.tm_year > lastLogMsgTm.tm_year) ||
                    (logMsgTm.tm_yday > lastLogMsgTm.tm_yday) ||
                    (logMsgTm.tm_hour > lastLogMsgTm.tm_hour))
                {
                    return true;
                }

                break;
            case TIME_INTERVAL_DAILY:
                if ((logMsgTm.tm_year > lastLogMsgTm.tm_year) ||
                    (logMsgTm.tm_yday > lastLogMsgTm.tm_yday))
                {
                    return true;
                }

                break;
            case TIME_INTERVAL_MONTHLY:
                if ((logMsgTm.tm_year > lastLogMsgTm.tm_year) ||
                    (logMsgTm.tm_mon > lastLogMsgTm.tm_mon))
                {
                    return true;
                }

                break;
            default:
                return false;
                break;
        }
    }

    return false;
}


void LogFileWriter::rotateLogFile()
{
    std::string rotatingPathStr = m_logFilePath.string() + "." +
            GDateTime::getDateString(false) + "." +
            GDateTime::getTimeString(GDateTime::PRECISION_MICROSECOND, false);

    fs::path rotatingPath(rotatingPathStr, fs::native);

    try
    {
        closeLogFile();
        fs::rename(m_logFilePath, rotatingPath);
        m_logFileSize = 0;

        openLogFile();
    }
    catch (const std::exception& e)
    {
        std::string errMessage = "Encounter error while rotating log file. ";
        errMessage += e.what();
        throw std::runtime_error(errMessage);
    }
}
